package com.xh.bussiness.xkw.controller;

import com.alibaba.fastjson.JSONObject;
import com.xh.bussiness.permission.model.User;
import com.xh.bussiness.permission.service.IUserService;
import com.xh.bussiness.xkw.constant.XkwCacheConstant;
import com.xh.bussiness.xkw.model.RelationShip;
import com.xh.bussiness.xkw.service.IXkwQuestionService;
import com.xh.bussiness.xkw.service.RelationShipService;
import com.xh.bussiness.xkw.service.impl.XkwQuestionServiceImpl;
import com.xh.bussiness.xkw.utils.AesUtils;
import com.xh.bussiness.xkw.utils.ConfigUtils;
import com.xh.bussiness.xkw.utils.SignatureUtils;
import com.xh.bussiness.xkw.utils.XkwHttpsUtils;
import com.xh.core.annotation.ExcludeLoginTokenIntercept;
import com.xh.core.util.CacheUtils;
import com.xh.core.util.UserUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.*;

/**
 * @Author: tw
 * @Description: 学科网认证登录执行类
 * @date: 2021-01-08 下午2:14
 */
@Controller
@RequestMapping("/xkw")
public class OauthClientController {

    @Autowired
    private RelationShipService relationShipService;
    @Autowired
    private IXkwQuestionService xkwQuestionService;
    @Autowired
    private IUserService userService;
    /**
     * 学科网分配的appkey
     */
    private final String appKey;
    /**
     * 学科网分配的appSecret
     */
    private final String appSecret;
    /**
     * 学科网的服务，如http://www.zxxk.com/、http://zujuan.xkw.com/等，注意域名后面的斜杠不能少，更多服务可联系客服获取
     */
    private final String service;
    /**
     * 学科网Oauth认证平台地址
     */
    private final String oauthServerUrl;

    private final String xkwCallbackHostUrl;

    Map<String, String> requestIds = new HashMap<>();


    @Autowired
    public OauthClientController(ConfigUtils configUtils) {
        appKey = configUtils.getAppKey();
        appSecret = configUtils.getAppSecret();
        service = configUtils.getService();
        oauthServerUrl = configUtils.getOauthServerUrl();
        xkwCallbackHostUrl = configUtils.getXkwCallbackHostUrl();
    }

    /**
     * 平台认证执行器
     */
    @RequestMapping(value = "/authentication", method = {RequestMethod.POST, RequestMethod.GET})
    @ExcludeLoginTokenIntercept
    public String processRequest(HttpServletRequest request, Model model,HttpSession session) throws Exception {
        //从缓存中获取userId，如果不存在说明用户未登录第三方应用，强制跳转第三方登录页面
        String userId = request.getParameter("userId");
        String requestId = request.getParameter("requestId");
        if(userId != null){
            session.setAttribute("userId", userId);
            session.setAttribute("requestId", requestId);
            System.out.println("###从request中获取userId###" + userId);
            System.out.println("###从request中获取requestId###" + requestId);
        }else{
            userId = (String) session.getAttribute("userId");
            requestId = (String) session.getAttribute("requestId");
            System.out.println("###从session中获取userId###" + userId);
            System.out.println("###从session中获取requestId###" + requestId);
        }
        System.out.println("###登录学科网用户id###" + userId);
        System.out.println("###requestId###" + requestId);
        User user = userService.getById(userId);
//        if (user == null) {
//            return "请重新登录！";
//        }
        //   User user = new User();
        //    user.setId("430ac3c94ec9484ab1c1ccebcc750c52");
        String phone = user.getMobile();
        if (null == requestId) {
            requestId = requestIds.get(user.getId());
        } else {
            requestIds.put(user.getId(), requestId);
        }

        //从http请求中获取code
        String code = request.getParameter("code");
        //动态获取本地的回调地址
        final String redirectUri = getRedirectUrl(request);
        String openId = "";
        //code为空,跳转学科网Oauth授权中心获取code
        if (StringUtils.isEmpty(code)) {
            //根据userId获取openId
            RelationShip relationShip = relationShipService.getByUserId(user.getId());
            if (relationShip != null) {
                openId = relationShip.getOpenId();
            }
            String url = getAuthorizeUrl(openId, redirectUri, phone, requestId);
//            System.out.println(url);
            return "redirect:" + url;
        }
        //获取accessToken
        String accessTokenResp = getAccessToken(code, redirectUri);
        String accessToken = getAccessTokenFromResp(accessTokenResp);
        if (StringUtils.isEmpty(accessToken)) {
            model.addAttribute("message", "认证失败，" + getErrorFromResp(accessTokenResp));
            return "failure";
        }
        //获取openId
        String profileResp = getProfile(accessToken);
        openId = getOpenIdFromResp(profileResp);

        System.out.println("openId" + openId);

        if (StringUtils.isEmpty(openId)) {
            model.addAttribute("message", "认证失败，" + getErrorFromResp(profileResp));
            return "failure";
        }

        /*
         * 解析xkw返回的新的openId，然后查询该用户是否已经绑定openId。如果未绑定，则添加相应的关联关系；
         * 如果已绑定，则更新最新的openId、addTime（维护用户和openId的关联关系）
         */
        boolean res = maintainUserRelationShip(user.getId(), openId);
        if (res) {
            //用户关联关系正确维护，跳转指定服务地址
            String urlParam = xkwCallbackHostUrl + requestId + "&_openid=" + openId;
            String serviceUrl = service + Base64.getUrlEncoder().encodeToString(urlParam.getBytes());

            String urlToRedirect = String.format(oauthServerUrl + "/login?service=%s", serviceUrl);
            return "redirect:" + urlToRedirect;
        }
        //未维护成功，跳转错的地址
        model.addAttribute("message", "认证成功，更新用户关联关系失败");
        return "failure";
    }

    /**
     * 获取Oauth授权地址
     *
     * @param openId      学科网返回给应用的OpenId
     * @param redirectUri 本系统的回调地址，用来处理Oauth接下来的逻辑
     * @param requestId   本系统的查询试题时用的唯一标识
     * @return 授权地址
     */
//    private String getAuthorizeUrl(String openId,String phone, String redirectUri,String requestId) throws Exception {
//        return getAuthorizeUrl(openId, redirectUri, phone,requestId);
//    }

    /**
     * 获取Oauth授权地址，除非对接时学科网有特别说明，否则不要使用该方法，您可以使用{@link OauthClientController
     *
     * @param openId      学科网返回给应用的OpenId，非空
     * @param extra       填写手机号，邮箱，用户名中的任意一项，推荐填写顺序从高到低，对接时学科网会特别说明，非空
     * @param redirectUri 本系统的回调地址，用来处理Oauth接下来的逻辑
     * @param requestId   本系统的查询试题时用的唯一标识
     * @return 授权地址
     */
    private String getAuthorizeUrl(String openId, String redirectUri, String extra, String requestId) throws Exception {
        String userOpenid = openId;
        openId = AesUtils.aesEncrypt(openId, appSecret);
        String timeSpan = AesUtils.aesEncrypt(getTimeStamp(), appSecret);
        String url = oauthServerUrl + "/oauth2/authorize";
        //此处必须使用SortedMap，主要是利用SortedMap能够根据参数名首字母自动进行排序的特性
        SortedMap<String, Object> paramMap = new TreeMap<>();
        paramMap.put("client_id", appKey);
        paramMap.put("open_id", openId);
        paramMap.put("service", service);
        if (!StringUtils.isEmpty(openId)) {
            String urlParam = xkwCallbackHostUrl + requestId + "&_openid=" + userOpenid;
            String serviceUrl = service + Base64.getUrlEncoder().encodeToString(urlParam.getBytes());
            paramMap.put("service", serviceUrl);
        }
        paramMap.put("redirect_uri", redirectUri);
        paramMap.put("timespan", timeSpan);
        paramMap.put("extra", StringUtils.isEmpty(extra) ? "" : extra);
        paramMap.put("signature", SignatureUtils.generateSignature(paramMap, appSecret));
        //加密之后参数中会含有特殊字符，如加号“+”，会导致浏览器url解析成空格，因此需要给参数值编一下码
        return url + "?" + getEncodedParamStr(paramMap);
    }

    /**
     * 内部方法：获取accessToken认证
     *
     * @param code        非空
     * @param redirectUri 回调地址，非空
     * @return 获取到的json字符串，内包含accessToken信息
     */
    private String getAccessToken(String code, String redirectUri) throws Exception {
        //此处必须使用SortedMap，主要是利用SortedMap能够根据参数名首字母自动进行排序的特性
        SortedMap<String, Object> paramMap = new TreeMap<>();
        paramMap.put("client_id", appKey);
        paramMap.put("code", code);
        paramMap.put("redirect_uri", redirectUri);
        paramMap.put("signature", SignatureUtils.generateSignature(paramMap, appSecret));
        String url = oauthServerUrl + "/oauth2/accessToken?" + getEncodedParamStr(paramMap);
        return XkwHttpsUtils.httpPost(url);
    }

    /**
     * 内部方法：获取OpenId
     *
     * @param accessToken 非空
     * @return 获取到的json字符串，内包含OpenId信息
     */
    private String getProfile(String accessToken) throws Exception {
        String url = String.format(oauthServerUrl + "/oauth2/profile?access_token=%s", accessToken);
        return XkwHttpsUtils.httpPost(url);
    }

    /**
     * 内部方法：获取OpenId，除非对接时学科网有特别说明，否则不要使用该方法，您可使用{@link OauthClientController#getProfile(String)}
     *
     * @param accessToken 非空
     * @param schoolId    由学科网分配，对接时学科网会特别说明该参数，非空
     * @return 获取到的json字符串，内包含OpenId信息
     */
    private String getProfile(String accessToken, Integer schoolId) throws Exception {
        String url = String.format(oauthServerUrl + "/oauth2/profile?access_token=%s&schoolId=%s", accessToken, schoolId);
        return XkwHttpsUtils.httpPost(url);
    }

    /**
     * 获取accessToken
     *
     * @param strResp respone响应字符串
     */
    private String getAccessTokenFromResp(String strResp) {
        return getFiledFormResp(strResp, "access_token");
    }

    /**
     * 获取openId
     *
     * @param strResp respone响应字符串
     */
    private String getOpenIdFromResp(String strResp) {
        return getFiledFormResp(strResp, "open_id");
    }

    /**
     * 获取error信息
     *
     * @param strResp respone响应字符串
     */
    private String getErrorFromResp(String strResp) {
        return getFiledFormResp(strResp, "error");
    }

    /**
     * 内部方法：从xkw返回数据中解析指定字段的数据值
     *
     * @param strResp respone响应字符串
     * @param filed   指定字段
     * @return 指定字段的值
     */
    private String getFiledFormResp(String strResp, String filed) {
        if (StringUtils.isEmpty(strResp) || StringUtils.isEmpty(filed)) {
            return "";
        }
        //将json数据转为map格式进行维护
        JSONObject jasonObject = JSONObject.parseObject(strResp);
        return (String) jasonObject.get(filed);
    }

    /**
     * 维护openId与userId的关联关系;在存储对应关系时，第三方应用需判断当前用户是否已经绑定openId，
     * 如果已经绑定则进行更新，如果未绑定则进行插入；
     */
    private boolean maintainUserRelationShip(String userId, String openId) throws Exception {
        if (StringUtils.isEmpty(openId) || userId == null) {
            return false;
        }
        RelationShip relationShip = relationShipService.getByUserId(userId);
        if (relationShip == null) {
            //插入用户关联关系
            return relationShipService.addUserRelationShip(userId, openId, new Date());
        }
        //更新用户关联关系
        return relationShipService.updateUserRelationShip(userId, openId, new Date());
    }

    /**
     * 内部方法：动态获取项目的回调地址
     */
    private static String getRedirectUrl(HttpServletRequest request) {
        String url = "";
        if (request.getServerPort() == 80) {
            url = String.format("%s://%s%s/xkw/authentication", request.getScheme(), request.getServerName(), request.getContextPath());
            return url;
        }
        url = String.format("%s://%s:%s%s/xkw/authentication", request.getScheme(), request.getServerName(), request.getServerPort(), request.getContextPath());
        return url;
    }

    /**
     * 内部方法：获取时间戳
     */
    private String getTimeStamp() {
        return String.valueOf(System.currentTimeMillis());
    }

    /**
     * 将所有的参数值进行编码，防止参数值中会参数特殊字符，如加号"+"，导致浏览器url解析成空格，因此需要给参数值编一下码
     *
     * @param paramMap 参数Map
     * @return
     * @throws UnsupportedEncodingException
     */
    private String getEncodedParamStr(Map<String, Object> paramMap) throws UnsupportedEncodingException {
        StringBuilder encodedParamStr = new StringBuilder();
        for (Map.Entry param : paramMap.entrySet()) {
            if (encodedParamStr.length() > 0) {
                encodedParamStr.append("&");
            }
            encodedParamStr.append(param.getKey()).append("=").append(URLEncoder.encode(param.getValue().toString(), "utf-8"));
        }
        return encodedParamStr.toString();
    }


    /**
     * 学科网回调该方法，需要配置拦截器放开
     *
     * @param openid  用户openId
     * @param paperid 学科网试卷id
     * @throws Exception
     */
    @RequestMapping(value = "/callback/{requestId}", method = RequestMethod.GET)
    @ExcludeLoginTokenIntercept
    public String callback(@PathVariable String requestId, String openid, String paperid, Model model) throws Exception {
//        if(null == paperid){
//            return ;
//        }
        System.out.println("userOpenid = " + openid);
        System.out.println("paperid = " + paperid);
//        paperid="4116";
//        openid="open-33-JuouJsxeLU";

        //回调参数：试卷id###paper[业务场景]###回调地址
        String[] params = requestId.split("@");


        String callBackUrl = null;
        if (XkwCacheConstant.BizType.PAPER.equals(params[1])) {
            model.addAttribute("type", XkwCacheConstant.BizType.PAPER);
            callBackUrl = String.format(XkwCacheConstant.PAPER_URL, params[2], params[0]);
        }else if(XkwCacheConstant.BizType.ENTRANCETEST.equals(params[1])){
            model.addAttribute("type", XkwCacheConstant.BizType.ENTRANCETEST);
            callBackUrl = String.format(XkwCacheConstant.PAPER_URL, params[2], params[0]);
        } else if (XkwCacheConstant.BizType.KNOWLEDGE.equals(params[1])) {
            model.addAttribute("type", XkwCacheConstant.BizType.KNOWLEDGE);
            callBackUrl = String.format(XkwCacheConstant.KNOWLEDGE_URL, params[2], params[0]);
        } else if (XkwCacheConstant.BizType.HANDOUT.equals(params[1])) {
            model.addAttribute("type", XkwCacheConstant.BizType.HANDOUT);
            callBackUrl = String.format(XkwCacheConstant.HANDOUT_URL, params[2], params[0]);
        }
        model.addAttribute("callBackUrl", callBackUrl);
        //判断是否执行完回调
        Object val = CacheUtils.get(String.format("%s_%s_%s", XkwCacheConstant.CALL_BACK, params[1], requestId));
        if (Objects.nonNull(val)) {
            model.addAttribute("size", val);
        }else{
            Integer size = xkwQuestionService.getXkwData(params[0], params[1], openid, paperid);
            model.addAttribute("size", size);
        }
        return "xkwsuccess";

    }


}
